﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;
using Microsoft.Web.Mvc;

namespace Peak.Web.Controllers.HtmlExtentions
{
  public static class ActionLinkExtensions
  {
    public static MvcHtmlString ActionLink2<TController>(this HtmlHelper html, Expression<Action<TController>> action, string linkText) where TController : Controller
    {
      RouteValueDictionary routeValuesFromExpression = GetRouteValuesFromExpression<TController>(action);
      RouteValueDictionary result = new RouteValueDictionary();
      foreach (KeyValuePair<string, object> pair in routeValuesFromExpression)
      {
        if(pair.Key.ToLower() != "action" && pair.Key.ToLower() != "controller")
        {
          if(!(pair.Value is string))
          {
            FillParams(result, pair);
          }
        }
        else
        {
          result.Add(pair.Key,pair.Value);
        }
      }
      return MvcHtmlString.Create(HtmlHelper.GenerateRouteLink(html.ViewContext.RequestContext, html.RouteCollection, linkText, null,result,new Dictionary<string, object>()));
    }

    private static void FillParams(RouteValueDictionary result, KeyValuePair<string, object> pair)
    {
      if (pair.Value != null)
      {
        PropertyInfo[] pis = pair.Value.GetType().GetProperties();
        foreach (PropertyInfo pi in pis)
        {
          result.Add(pair.Key + "." + pi.Name, pi.GetValue(pair.Value, null));
        }
      }
    }

    // Methods
    private static void AddParameterValuesFromExpressionToDictionary(RouteValueDictionary rvd, MethodCallExpression call)
    {
      ParameterInfo[] parameters = call.Method.GetParameters();
      if (parameters.Length > 0)
      {
        for (int i = 0; i < parameters.Length; i++)
        {
          Expression arg = call.Arguments[i];
          object obj2 = null;
          ConstantExpression expression2 = arg as ConstantExpression;
          if (expression2 != null)
          {
            obj2 = expression2.Value;
          }
          else
          {
            obj2 = CachedExpressionCompiler.Evaluate(arg);
          }
          rvd.Add(parameters[i].Name, obj2);
        }
      }
    }

    public static string GetInputName<TModel, TProperty>(Expression<Func<TModel, TProperty>> expression)
    {
      if (expression.Body.NodeType == ExpressionType.Call)
      {
        MethodCallExpression body = (MethodCallExpression)expression.Body;
        return GetInputName(body).Substring(expression.Parameters[0].Name.Length + 1);
      }
      return expression.Body.ToString().Substring(expression.Parameters[0].Name.Length + 1);
    }

    private static string GetInputName(MethodCallExpression expression)
    {
      MethodCallExpression expression2 = expression.Object as MethodCallExpression;
      if (expression2 != null)
      {
        return GetInputName(expression2);
      }
      return expression.Object.ToString();
    }

    public static RouteValueDictionary GetRouteValuesFromExpression<TController>(Expression<Action<TController>> action) where TController : Controller
    {
      if (action == null)
      {
        throw new ArgumentNullException("action");
      }
      MethodCallExpression body = action.Body as MethodCallExpression;
      if (body == null)
      {
        throw new ArgumentException("MustBeMethodCall", "action");
      }
      string name = typeof(TController).Name;
      if (!name.EndsWith("Controller", StringComparison.OrdinalIgnoreCase))
      {
        throw new ArgumentException("TargetMustEndInController", "action");
      }
      name = name.Substring(0, name.Length - "Controller".Length);
      if (name.Length == 0)
      {
        throw new ArgumentException("CannotRouteToController", "action");
      }
      string targetActionName = GetTargetActionName(body.Method);
      RouteValueDictionary rvd = new RouteValueDictionary();
      rvd.Add("Controller", name);
      rvd.Add("Action", targetActionName);
      ActionLinkAreaAttribute attribute = typeof(TController).GetCustomAttributes(typeof(ActionLinkAreaAttribute), true).FirstOrDefault<object>() as ActionLinkAreaAttribute;
      if (attribute != null)
      {
        string area = attribute.Area;
        rvd.Add("Area", area);
      }
      AddParameterValuesFromExpressionToDictionary(rvd, body);
      return rvd;
    }

    private static string GetTargetActionName(MethodInfo methodInfo)
    {
      string name = methodInfo.Name;
      if (methodInfo.IsDefined(typeof(NonActionAttribute), true))
      {
        throw new InvalidOperationException(string.Format(CultureInfo.CurrentUICulture, "CannotCallNonAction", new object[] { name }));
      }
      ActionNameAttribute attribute = methodInfo.GetCustomAttributes(typeof(ActionNameAttribute), true).OfType<ActionNameAttribute>().FirstOrDefault<ActionNameAttribute>();
      if (attribute != null)
      {
        return attribute.Name;
      }
      if (methodInfo.DeclaringType.IsSubclassOf(typeof(AsyncController)))
      {
        if (name.EndsWith("Async", StringComparison.OrdinalIgnoreCase))
        {
          return name.Substring(0, name.Length - "Async".Length);
        }
        if (name.EndsWith("Completed", StringComparison.OrdinalIgnoreCase))
        {
          throw new InvalidOperationException(string.Format(CultureInfo.CurrentUICulture, "CannotCallCompletedMethod", new object[] { name }));
        }
      }
      return name;
    }
  }
}
